# -*- coding: UTF-8 -*-
"""
@Project , trainee
@File    , app.py
@IDE     , PyCharm
@Author  , 2607750505@qq.com
@Date    , 2025/6/16 15:26
"""
# 导入工具文件中的函数
from tools.actor import *
from tools.addressData import *
from tools.homeData import *
from tools.rateData import *
from tools.timeData import *
from tools.typeData import *
from tools.word_cloud import *
from tools.type_year_analysisData import *
from tools.getData import mainFun
# 导入Flask相关模块
from flask import Flask, render_template, request, jsonify
from flask import redirect, url_for, session
import pandas as pd
# 导入pyecharts图表和配置项
from pyecharts.charts import *  # 导入所有的图表
from pyecharts import options as opts  # 导入配置项
from functools import wraps  # 用于装饰器

# 创建Flask应用实例，指定静态文件和模板文件路径
app = Flask(__name__, static_folder='static', template_folder='templates')
# 设置Session密钥，用于加密会话数据
app.secret_key = 'ywqqq'


# 登录检查装饰器，用于保护需要登录才能访问的路由
def login_required(f):
    @wraps(f)  # 保留被装饰函数的元数据
    def decorated_function(*args, **kwargs):
        # 检查Session中是否存在用户名
        if 'username' not in session:
            # 未登录则重定向到登录页面
            return redirect(url_for('login'))
        # 已登录则执行原函数
        return f(*args, **kwargs)
    return decorated_function


# 根路由，重定向到登录页面
@app.route('/')
def rootRoute():
    return render_template('login.html')


# 注册路由处理
@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        # 获取表单提交的用户名和密码
        username = request.form['username']
        password = request.form['password']
        # 验证用户名和密码是否为空
        if not username or not password:
            # 信息不完整，重定向回注册页面
            return redirect(url_for('register'))
        # 验证两次输入的密码是否一致
        if request.form['password'] != request.form['passwordCheked']:
            return '<h1>两次密码不一致！！！</h1>'

        # 连接数据库检查用户名是否已存在
        conn, cursor = get_conn()
        cursor.execute('select username from users')
        data = cursor.fetchall()
        userList = [user[0] for user in data]
        if username in userList:
            return '<h1>用户名已经存在！！！</h1>'

        # 生成唯一的6位数账户
        while True:
            account = str(random.randint(100000, 999999))
            cursor.execute('SELECT account FROM users WHERE account = %s', (account,))
            existing_account = cursor.fetchone()
            if not existing_account:
                break

        # 将用户信息写入数据库
        cursor.execute('INSERT INTO users (username, password, account) VALUES (%s, %s, %s)',
                       (username, password, account))
        conn.commit()
        conn.close()
        cursor.close()
        # 注册成功，跳转到提示页面并传递生成的账户
        return render_template('registration_success.html', account=account)
    # GET请求时显示注册页面
    return render_template('register.html')


# 登录路由处理
@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None  # 初始化错误信息
    if request.method == 'POST':
        # 获取表单提交的账户和密码
        account = request.form.get('account')
        password = request.form.get('password')
        # 验证账户和密码是否为空
        if not account or not password:
            error = '账号和密码不能为空'
        else:
            # 查询数据库验证用户
            conn, cursor = get_conn()
            query = 'SELECT * FROM users WHERE account = %s AND password = %s'
            cursor.execute(query, (account, password))
            user = cursor.fetchone()  # 找到一个即可
            if user:  # 如果存在用户名，则保存用户名到Session中，并且跳转到home界面
                # 将用户名存入Session
                session['username'] = user[1]
                # 重定向到首页
                return redirect(url_for('home'))
            else:
                error = '账号或密码错误'
    # 显示登录页面并传递错误信息（如果有）
    return render_template('login.html', error=error)


# 登出路由处理
@app.route('/logout')
def logout():
    # 清除用户登录态（比如删除 Session 中保存的用户名等信息）
    session.pop('username', None)
    # 重定向到登录页面
    return redirect(url_for('login'))


# 数据筛选API接口
@app.route('/api/filtered_data', methods=['GET'])
def get_filtered_data():
    """根据年份和类型筛选电影数据"""
    # 获取URL参数中的筛选条件
    year_start = request.args.get('year_start', 1900)
    year_end = request.args.get('year_end', 2025)
    movie_type = request.args.get('movie_type', '')

    allData = getAllData()  # 获取所有电影数据
    filtered_data = []  # 初始化筛选结果列表

    for item in allData:
        # 年份筛选逻辑
        try:
            year = int(item[6] or 0)
            if year < int(year_start) or year > int(year_end):
                continue    # 不符合年份条件，跳过
        except:
            continue    # 年份解析错误，跳过

        # 类型筛选
        if movie_type and movie_type not in item[7]:
            continue    # 不符合类型条件，跳过

        # 将符合条件的数据添加到结果列表
        filtered_data.append({
            'id': item[0],
            'title': item[3],
            'rate': item[2],
            'year': item[6],
            'types': item[7],
            'country': item[8]
        })

    # 将筛选结果转换为JSON格式返回
    return jsonify(filtered_data)


# 首页
@app.route('/home')
@login_required      # 需要登录才能访问
def home():
    username = session['username']
    # 获取首页需要显示的所有信息，并从后台--前端进行展示出来
    allData = getAllData()  # 获取所有的电影数据
    dataLen = len(allData)  # 统计电影长度
    maxRate = getMaxRate()  # 电影最高的分数
    maxCast = getMaxCast()  # 出场最多的演员
    typeAll = getTypesAll()  # 获取电影种类数据
    typeAll = len(typeAll)  # 统计种类长度
    maxLang = getMaxLang()  # 获取电影语言数据

    # 新增：获取各国电影数量数据
    countryData = getCountryData()

    # 新增：获取语种数量数据
    langData = getLangData()
    langX = langData['langX']  # 语种名称列表
    langY = langData['langY']  # 对应数量列表

    # 获取图标需要的数据
    types = getType_t()  # 电影种类饼状图的数据
    x, y = getRate_t()  # 电影评分折线图的数据

    return render_template('home.html', username=username,
                           dataLen=dataLen, maxRate=maxRate, maxCast=maxCast,
                           typeAll=typeAll, maxLang=maxLang, types=types,
                           x=list(x), y=list(y),
                           countryData=countryData, langX=langX, langY=langY)  # 新增数据传递


# search 搜索电影信息的请求
@app.route('/search/<int:searchId>', methods=['GET', 'POST'])
@login_required
def search(searchId):
    # 获取username
    username = session['username']
    # 获取所有的电影数据
    allData = getAllData()
    # data
    data = []
    # 请求方式的判断  GET 【id的查询】 POST 【根据关键字查询】
    if request.method == 'GET':
        if searchId == 0:
            return render_template('search.html', username=username, data=data)
        # id不为空时，则遍历allData，然后获取到id相同的电影数据
        for i in allData:
            if i[0] == searchId:
                data.append(i)
        return render_template('search.html', username=username, data=data)
    else:  # POST请求
        searchWord = request.form['searchIpt']
        # 如果搜索框为空，则重定向回该界面
        if not searchWord:
            return redirect(url_for('search', searchId=searchId))

        # 不为空、进行过滤的处理
        # 定义一个函数来进行数据过滤【根据title来查找过滤】
        def filterFun(item):
            if item[3].find(searchWord) == -1:
                return False
            else:
                return True

        # 调用函数来进行过滤
        data = list(filter(filterFun, allData))
        return render_template('search.html', username=username, data=data)


# 实现时间分析表
@app.route('/time_t')
@login_required
def time_t():
    username = session['username']
    x, y = getTimeList()  # 历年产量统计
    movieTimeData = getMovieTimeList()  # 电影数据时长分布占比
    return render_template('time_t.html', username=username,
                           x=list(x), y=list(y), movieTimeData=movieTimeData)


# 实现评分分析表
@app.route('/rate_t/<type>', methods=['GET', 'POST'])
@login_required
def rate_t(type):
    username = session['username']
    # 电影类型评分统计
    typeAll = getTypesAll()  # 获取电影种类
    if type == 'all':
        x, y = getRate_t()
    else:
        x, y = getRate_tType(type)  # 获取不同类型电影的数据
    # 星级饼状图
    if request.method == 'GET':
        star, movieName = getStart('')  # 默认获取第一部电影
    else:
        searchWord = request.form['searchIpt'].strip()
        try:  # 如果没有问题就获取输入电影的星级占比和名称
            star, movieName = getStart(searchWord)
        except Exception as e:
            return redirect(url_for('rate_t', type=type))
    # 年度评价评分柱状图
    x1, y1 = getMean()
    y1 = [round(y1_val, 2) for y1_val in y1]
    # 电影中外评分分布图
    x2, y2, y22 = getCountryRating()
    return render_template('rate_t.html', username=username,
                           x=list(x), y=list(y), star=star, movieName=movieName,
                           x1=x1, y1=y1, x2=x2, y2=y2, y22=y22, typeAll=typeAll)


@app.route('/type_year_analysis/<int:typeId>', methods=['GET', 'POST'])
@login_required
def type_year_analysis(typeId):
    username = session.get('username')
    if not username:
        return redirect(url_for('login'))

    # 1. 获取所有电影类型
    all_types = get_movie_types()

    # 2. 为前10个类型添加索引（从1开始，对应typeId）
    indexed_types = []
    for index, type_name in enumerate(all_types[:10]):
        indexed_types.append((index + 1, type_name))  # (ID, 类型名)

    # 3. 处理搜索逻辑（保持原有代码）
    target_type = None
    if request.method == 'POST':
        target_type = request.form.get('search_type').strip()
        if not target_type:
            return redirect(url_for('type_year_analysis', typeId=0))
    elif typeId != 0:
        try:
            target_type = all_types[typeId - 1]  # 类型ID从1开始
        except (IndexError, ValueError):
            return redirect(url_for('type_year_analysis', typeId=0))

    # 4. 获取统计数据（保持原有代码）
    years, counts = get_type_year_data(target_type)

    # 5. 传递带索引的类型列表到模板
    return render_template(
        'type_year_analysis.html',
        username=username,
        target_type=target_type or "全部类型",
        years=years,
        counts=counts,
        indexed_types=indexed_types  # 新增：带索引的类型列表
    )


# app.py 中的 address_t 路由
from tools import homeData  # 确保导入 homeData


@app.route('/address_t')
@login_required
def address_t():
    username = session['username']
    x, y = getAddressData()  # 拍摄地点数据（addressData.py 中的函数）

    # 调用 homeData.py 中的 getLangData()，返回字典
    lang_data = homeData.getLangData()
    x1 = lang_data['langX']  # 语言名称
    y1 = lang_data['langY']  # 数量

    print('x1:', x1, 'y1:', y1)
    return render_template('address_t.html',
                           username=username,
                           x=x, y=y,
                           x1=x1, y1=y1)


# 实现类型分析表
@app.route('/type_t')
@login_required
def type_t():
    username = session['username']
    result = getMovieTypeData()
    data = sorted(result, key=lambda x: x['value'], reverse=True)
    top10Data = data[:10]
    return render_template('type_t.html', username=username, top10Data=top10Data)


# 实现导演与演员
@app.route('/actor_t')
def actor_t():
    username = session['username']
    x, y = getAllActorMovieNum()  # 导演作品数量前20
    x1, y1 = getAllDirectorMovieNum()  # 演员参演排名前20
    return render_template('actor_t.html', username=username, x=x, y=y, x1=x1, y1=y1)


# 数据操做
@app.route('/tables/<int:id>')
@login_required
def tables(id):
    username = session['username']
    tablelist = []
    if id == 0:
        tablelist = getTableList()
    return render_template('tables.html', username=username, tablelist=tablelist)


import stylecloud
from tools.getDataBase import get_conn


# 实现标题词云
# 需要绘制的字段、图标名称、输出名称
def getTitleImg(field, icon_name, output_name):
    sql = f'select {field} from movies'
    conn, cursor = get_conn()
    cursor.execute(sql)
    data = cursor.fetchall()  # 获取查询结果
    text1 = ','.join([row[0] for row in data])
    text1 = ','.join(text1)  # 这个操作是将标题拆分为一个一个的字
    stylecloud.gen_stylecloud(text=text1, icon_name=icon_name,
                              output_name=output_name, font_path='/static/fonts/simhei.ttf')


# 实现标题词云
@app.route('/title_c')
@login_required
def title_c():
    username = session['username']
    # 调用词云文件中的函数，进行绘制词云
    getTitleImg('title', 'fas fa-heart', './static/images/title.png')
    return render_template('title_c.html', username=username)


# 实现演员名词云
@app.route('/casts_c')
@login_required
def casts_c():
    username = session['username']
    # 调用词云文件中的函数，进行绘制词云
    getCastsImg('casts', 'fab fa-apple', './static/images/casts.png')
    return render_template('casts_c.html', username=username)


# 实现自定义词云
@app.route('/comments_c', methods=['GET', 'POST'])
@login_required
def comments_c():
    username = session['username']
    error_msg = None

    if request.method == 'POST':
        searchWord = request.form['searchIpt']
        if not searchWord:
            error_msg = "请输入电影名称"
        else:
            success = getCommentsImg('commentContent', searchWord, 'fab fa-qq', './static/images/comments.png')
            if not success:
                error_msg = f"无法生成电影 '{searchWord}' 的词云，请检查电影名称是否正确"

    return render_template('comments_c.html', username=username, error_msg=error_msg)


# 实现大屏可视化界面
@app.route('/analysis1', methods=['GET', 'POST'])
@login_required
def index():
    # 原有数据获取...
    allData = getAllData()
    allData = len(allData)
    maxRate = getMaxRate()
    typeAll = getTypesAll()
    typeAll = len(typeAll)
    maxLang = getMaxLang()[:2]
    type_distribution = getTypeDistribution() or []

    # **新增：生成图表 HTML**
    chart_html1 = typeData()  # 电影类型TOP5
    chart_html2 = yearData()  # 年份电影数据量
    chart_html3 = langData()  # 中英文电影占比
    chart_html4 = commentData()  # 电影评论数折线图

    return render_template('analysis.html',
                           data={
                               'allData': allData,
                               'maxRate': maxRate,
                               'typeAll': typeAll,
                               'maxLang': maxLang,
                               'typeDistribution': type_distribution
                           },
                           chart_html1=chart_html1,
                           chart_html2=chart_html2,
                           chart_html3=chart_html3,
                           chart_html4=chart_html4)


# 电影类型统计
def typeData():
    # 读取数据
    data = pd.read_csv('./tools/data/type_counts.csv', encoding='utf-8')
    # [('剧情', 113), ('喜剧', 47), ('冒险', 41), ('奇幻', 38), ('爱情', 34)]
    pie_data = list(zip(data['类型'], data['数量']))
    pie = Pie(init_opts=opts.InitOpts(height='400px', width='500px'))
    pie.add('数量',
            pie_data,
            radius=['30%', '60%'],
            label_opts=opts.LabelOpts(formatter='{b}:{c}个',
                                      font_size=16,
                                      font_style='bold',
                                      color='#0f0')
            )
    # 设置配置项
    pie.set_global_opts(
        title_opts=opts.TitleOpts(title=""),
        tooltip_opts=opts.TooltipOpts(axis_pointer_type='shadow'),
        legend_opts=opts.LegendOpts(textstyle_opts=opts.TextStyleOpts(font_size=16, color='#0f0')),

    )
    pie.render('static/html/type_data.html')
    chart_html1 = pie.render_embed()
    return chart_html1


# 年份电影数据量
def yearData():
    data = pd.read_csv('./tools/data/year_counts.csv', encoding='utf-8')
    year = data['年份'].tolist()
    counts = data['数量'].tolist()
    bar = Bar(init_opts=opts.InitOpts(height='400px', width='500px'))
    bar.add_xaxis(year)
    bar.add_yaxis('数量', counts)
    bar.set_global_opts(
        title_opts=opts.TitleOpts(title=''),
        xaxis_opts=opts.AxisOpts(name='年份',
                                 name_textstyle_opts=opts.TextStyleOpts(color='#0f0'),
                                 axislabel_opts=opts.LabelOpts(color='#0f0', font_size=12),
                                 name_location='middle',
                                 name_gap=25
                                 ),
        yaxis_opts=opts.AxisOpts(name='数量',
                                 name_textstyle_opts=opts.TextStyleOpts(color='#0f0'),
                                 axislabel_opts=opts.LabelOpts(color='#0f0', font_size=12),
                                 name_location='middle',
                                 name_gap=25
                                 ),
        legend_opts=opts.LegendOpts(pos_left='center', textstyle_opts=opts.TextStyleOpts(color='#0f0')),
        tooltip_opts=opts.TooltipOpts(trigger='axis', axis_pointer_type='cross')
    )
    bar.set_series_opts(
        itemstyle_opts=opts.ItemStyleOpts(color='#0ff'),
        linestyle_opts=opts.LineStyleOpts(color='#f00'),
        label_opts=opts.LabelOpts(color='#000', font_size=12, rotate=45)
    )
    bar.render('static/html/year_data.html')
    chart_html2 = bar.render_embed()
    return chart_html2


# 中英文电影占比
def langData():
    data = pd.read_csv('./tools/data/lang_counts.csv', encoding='utf-8')
    # [['汉语普通话', 57], ['英语', 37]]
    result = [[row['语言'], row['数量']] for index, row in data.iterrows()]
    sum_value = result[0][1] + result[1][1]  # 计算中英文电影的总和，用户计算百分比
    cValue = [result[0][1] / sum_value]  # 计算中文占比
    eValue = [result[1][1] / sum_value]  # 计算英文占比
    # 中文水球图
    li1 = Liquid(init_opts=opts.InitOpts(height='100px', width='100px'))
    li1.add(
        '占比',
        cValue,
        center=['15%', '35%'],
        color=['#0f0'],
        is_outline_show=False,
        shape='pin',
        label_opts=opts.LabelOpts(font_size=40),
    )
    li1.set_global_opts(title_opts=opts.TitleOpts(title=result[0][0][:2],
                                                  pos_left='13%',
                                                  pos_top='3%',
                                                  title_textstyle_opts=opts.TextStyleOpts(color='#0f0')))

    # 英文水球图
    li2 = Liquid(init_opts=opts.InitOpts(height='100px', width='100px'))
    li2.add(
        '占比',
        eValue,
        center=['35%', '35%'],
        color=['#0f0'],
        is_outline_show=False,
        shape='pin',
        label_opts=opts.LabelOpts(font_size=40),
    )
    li2.set_global_opts(title_opts=opts.TitleOpts(title=result[1][0][:2],
                                                  pos_left='32%',
                                                  pos_top='3%',
                                                  title_textstyle_opts=opts.TextStyleOpts(color='#0f0')))
    # 组合
    grid = Grid()
    grid.add(li1, grid_opts=opts.GridOpts())
    grid.add(li2, grid_opts=opts.GridOpts())
    grid.render('static/html/lang_data.html')
    chart_html3 = grid.render_embed()
    return chart_html3


# 电影评论数据
def commentData():
    data = pd.read_csv('./tools/data/comment_counts.csv', encoding='utf-8')

    # 按电影名称去重，保留评论数最高的记录
    data = data.sort_values('数量', ascending=False).drop_duplicates('电影')

    names = data['电影'].tolist()
    counts = data['数量'].tolist()

    # 后续图表生成代码保持不变...
    bar = Bar(init_opts=opts.InitOpts(height='400px', width='500px'))
    bar.add_xaxis(names)
    bar.add_yaxis('评论数量', counts)

    bar.set_global_opts(
        title_opts=opts.TitleOpts(title=''),
        xaxis_opts=opts.AxisOpts(
            name='电影',
            name_textstyle_opts=opts.TextStyleOpts(color='#0f0'),
            axislabel_opts=opts.LabelOpts(font_size=12, rotate=45, color='#0f0')
        ),
        yaxis_opts=opts.AxisOpts(
            name='数量',
            min_=500000,
            max_=3500000,
            name_textstyle_opts=opts.TextStyleOpts(color='#0f0'),
            axislabel_opts=opts.LabelOpts(font_size=12, rotate=45, color='#0f0')
        ),
        legend_opts=opts.LegendOpts(pos_left='center', textstyle_opts=opts.TextStyleOpts(color='#0f0')),
        tooltip_opts=opts.TooltipOpts(trigger='axis', axis_pointer_type='cross')
    )
    bar.set_series_opts(
        itemstyle_opts=opts.ItemStyleOpts(color='#99ffcc'),
        linestyle_opts=opts.LineStyleOpts(color='#f00'),
        label_opts=opts.LabelOpts(color='#000', font_size=12, rotate=45)
    )

    bar.render('static/html/comment_data.html')
    chart_html4 = bar.render_embed()
    return chart_html4


# 实现地球
def worldData():
    data = pd.read_csv('./tools/data/country_counts.csv', encoding='utf-8')
    # [['中国大陆', 87], ['美国', 65], ['中国香港', 21], ['英国', 20], ['日本', 12],
    # ['法国', 10], ['加拿大', 5], ['意大利', 5], ['中国台湾', 4], ['德国', 4]]
    data_list = data.values.tolist()
    # 提供国家地区数据，用于替换原来数据中的国家或地区的名称
    country_name = ['Russia', 'Canada', 'China', 'USA', 'Brazil', 'Australia', 'India', 'Argentina', 'France', 'Japan']
    # 进行替换
    replace_data = []
    for i in range(len(country_name)):
        replace_data.append([country_name[i], data_list[i][1]])
    map = MapGlobe(init_opts=opts.InitOpts(height='500px', width='450px'))
    map.add(maptype='world', series_name='数量', data_pair=replace_data)
    map.set_global_opts(
        visualmap_opts=opts.VisualMapOpts(
            min_=0,
            max_=100,
            type_='color',
            range_color=['#0f0', '#0f0'],
        )
    )
    map.render('static/html/map3d.html')


if __name__ == '__main__':
    with app.app_context():
        mainFun()
    app.run(debug=True, host='0.0.0.0', port=8000)
